#include "utils.h"

#include <stdio.h>
#include <map>
#include "geometry/point.h"
#include "geometry/normal.h"
#include "shapes/triangle_mesh.h"
#include "error_gl.h"
#include "image_texture_gl.h"

struct Edge {
  GLuint pIndex[2]; //position indices

  bool operator< (const Edge &rhs) const {
    return ( pIndex[0] == rhs.pIndex[0]) ? ( pIndex[1] < rhs.pIndex[1]) : pIndex[0] < rhs.pIndex[0];
  }

  Edge( GLuint v0, GLuint v1) {
    pIndex[0] = std::min( v0, v1);
    pIndex[1] = std::max( v0, v1);
  }

private:
  Edge() {} // disallow the default constructor
};

namespace venus {
ModelGL::ModelGL() {
  glGenBuffers(1, &point_array_id_);
  glGenBuffers(1, &normal_array_id_);
  glGenBuffers(1, &element_array_id_);
}

ModelGL::~ModelGL() {
  glDeleteBuffers(1, &point_array_id_);
  glDeleteBuffers(1, &normal_array_id_);
  glDeleteBuffers(1, &element_array_id_);
}

void ModelGL::SetData(const TriangleMesh *mesh) {
  assert(NULL != mesh);
  if (NULL == mesh) return;
  mesh_ = mesh;
  bound_ = mesh_->Bound();
  int point_size = mesh->vertex_count() * sizeof(Point);
  int normal_size = mesh->vertex_count() * sizeof(Normal);
  glBindBuffer(GL_ARRAY_BUFFER, point_array_id_);
  glBufferData(GL_ARRAY_BUFFER, point_size , mesh->points(), GL_STATIC_DRAW);

  glBindBuffer(GL_ARRAY_BUFFER, normal_array_id_);
  glBufferData(GL_ARRAY_BUFFER, normal_size, mesh->normals(), GL_STATIC_DRAW);
  glBindBuffer(GL_ARRAY_BUFFER, 0);

  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_array_id_);
  glBufferData(GL_ELEMENT_ARRAY_BUFFER, 
               sizeof(int) * TRIPLE(mesh->triangle_count()),
               mesh->vertex_indices(), GL_STATIC_DRAW);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
  CheckErrorsGL("ModelGL::SetData");
}

bool ModelGL::Draw() {
  glBindBuffer(GL_ARRAY_BUFFER, point_array_id_);
  glVertexPointer(3, GL_FLOAT, sizeof(Point), NULL);
  glBindBuffer(GL_ARRAY_BUFFER, normal_array_id_);
  glNormalPointer(GL_FLOAT, sizeof(Normal), NULL);
  glBindBuffer(GL_ARRAY_BUFFER, 0);

  glEnableClientState(GL_VERTEX_ARRAY);
  glEnableClientState(GL_NORMAL_ARRAY);

  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_array_id_);
  glDrawElements(GL_TRIANGLES, TRIPLE(mesh_->triangle_count()), GL_UNSIGNED_INT,
                 NULL);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
  glDisableClientState(GL_VERTEX_ARRAY);
  glDisableClientState(GL_NORMAL_ARRAY);
  if (CheckErrorsGL("ModelGL::Draw")) return false;
  return true;
}

bool ModelGL::DrawWidthAdjacency() {
  glBindBuffer(GL_ARRAY_BUFFER, point_array_id_);
  glVertexPointer(3, GL_FLOAT, sizeof(Point), NULL);
  glBindBuffer(GL_ARRAY_BUFFER, normal_array_id_);
  glNormalPointer(GL_FLOAT, sizeof(Normal), NULL);
  glBindBuffer(GL_ARRAY_BUFFER, 0);

  glEnableClientState(GL_VERTEX_ARRAY);
  glEnableClientState(GL_NORMAL_ARRAY);

  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_array_id_);
  glDrawElements(GL_TRIANGLES_ADJACENCY_EXT, TRIPLE(mesh_->triangle_count()), 
                 GL_UNSIGNED_INT, NULL);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
  glDisableClientState(GL_VERTEX_ARRAY);
  glDisableClientState(GL_NORMAL_ARRAY);
  if (CheckErrorsGL("ModelGL::Draw")) return false;
  return true;
}

void ModelGL::DrawBound() {  
  glBegin(GL_QUADS);
  glVertex3f(bound_.minP.x, bound_.minP.y, bound_.minP.z);
  glVertex3f(bound_.maxP.x, bound_.minP.y, bound_.minP.z);
  glVertex3f(bound_.maxP.x, bound_.maxP.y, bound_.minP.z);
  glVertex3f(bound_.minP.x, bound_.maxP.y, bound_.minP.z);

  glVertex3f(bound_.minP.x, bound_.minP.y, bound_.maxP.z);
  glVertex3f(bound_.maxP.x, bound_.minP.y, bound_.maxP.z);
  glVertex3f(bound_.maxP.x, bound_.maxP.y, bound_.maxP.z);
  glVertex3f(bound_.minP.x, bound_.maxP.y, bound_.maxP.z);

  glVertex3f(bound_.minP.x, bound_.minP.y, bound_.minP.z);
  glVertex3f(bound_.maxP.x, bound_.minP.y, bound_.minP.z);
  glVertex3f(bound_.maxP.x, bound_.minP.y, bound_.maxP.z);
  glVertex3f(bound_.minP.x, bound_.minP.y, bound_.maxP.z);

  glVertex3f(bound_.minP.x, bound_.maxP.y, bound_.minP.z);
  glVertex3f(bound_.maxP.x, bound_.maxP.y, bound_.minP.z);
  glVertex3f(bound_.maxP.x, bound_.maxP.y, bound_.maxP.z);
  glVertex3f(bound_.minP.x, bound_.maxP.y, bound_.maxP.z);
  
  glVertex3f(bound_.minP.x, bound_.minP.y, bound_.minP.z);
  glVertex3f(bound_.minP.x, bound_.minP.y, bound_.maxP.z);
  glVertex3f(bound_.minP.x, bound_.maxP.y, bound_.maxP.z);
  glVertex3f(bound_.minP.x, bound_.maxP.y, bound_.minP.z);

  glVertex3f(bound_.maxP.x, bound_.minP.y, bound_.minP.z);
  glVertex3f(bound_.maxP.x, bound_.minP.y, bound_.maxP.z);
  glVertex3f(bound_.maxP.x, bound_.maxP.y, bound_.maxP.z);
  glVertex3f(bound_.maxP.x, bound_.maxP.y, bound_.minP.z);
  glEnd();
}

void ModelGL::Initialize( const TriangleMesh *mesh, int opts /*= kTriangles*/ ) {
  //typedef std::multimap<Edge, int> EdgeMap;
  //bool need_edges = opts & kEdges;
  //bool need_triangles_with_Adj = opts & kTrianglesWithAdjacency;
  //if (need_edges || need_triangles_with_Adj) {
  //  EdgeMap edges;
  //  int size = static_cast<int>(mesh->vertex_count());
  //  for (int i = 0; i < size; i += 3) { 
  //    for (int j = 0; j < 3; ++j) {
  //      Edge w(mesh->vertex_index(i+j), mesh->vertex_index(i+(j+1)%3));
  //      EdgeMap::iterator it = edges.find(w);
  //      if (need_edges && it == edges.end()) {
  //        //add edge indices
  //      }
  //      edges.insert(w, i / 3);
  //    }
  //  }
  //  if (need_triangles_with_Adj) {
  //    for (int i = 0; i <size; i += 3) {
  //      for (int j = 0; j < 3; ++j) {
  //        Edge w(mesh->vertex_index(i+j), mesh->vertex_index(i+(j+1)%3));
  //        EdgeMap::iterator it = edges.lower_bound(w);
  //        EdgeMap::iterator limit = edges.upper_bound(w);
  //        int adj_vertex = 0;
  //        int triangle_idx = i / 3;
  //        while (it != limit && it->second == triangle_idx) ++it;
  //        if (it == limit || it->first.pIndex[0] != w.pIndex[0] || 
  //            it->first.pIndex[1] != w.pIndex[i]) {
  //          adj_vertex = mesh->vertex_index(i+j);
  //          //++open_edges_;
  //        } else {
  //          int tri_offset = it->second * 3;
  //          adj_vertex = mesh->vertex_index(tri_offset);
  //          for (int k = 0; k < 3; ++k) {
  //            if (m)
  //          }
  //        }
  //      }
  //    }
  //  }
  //}
}

void DrawTriangleMesh(const TriangleMesh *mesh) {
  CheckErrorsGL("DrawTriangleMesh, begin");
  glVertexPointer(3, GL_FLOAT, sizeof(Point), mesh->points());
  glNormalPointer(GL_FLOAT, sizeof(Normal), mesh->normals());
  glEnableClientState(GL_VERTEX_ARRAY);
  glEnableClientState(GL_NORMAL_ARRAY);
  //other settings, eg. texture coordinates
  glDrawElements(GL_TRIANGLES, TRIPLE(mesh->triangle_count()), GL_UNSIGNED_INT,
                 mesh->vertex_indices());
  //glDrawArrays(GL_TRIANGLES, 0, mesh->vertex_count());
  glDisableClientState(GL_VERTEX_ARRAY);
  glDisableClientState(GL_NORMAL_ARRAY);
  CheckErrorsGL("DrawTriangleMesh, end");
}

void DrawQuad(float x1, float y1, float x2, float y2) {
  glBegin(GL_QUADS);
  glTexCoord2f(x1, y1); glVertex2f(x1, y1);
  glTexCoord2f(x1, y2); glVertex2f(x1, y2);
  glTexCoord2f(x2, y2); glVertex2f(x2, y2);
  glTexCoord2f(x2, y1); glVertex2f(x2, y1);
  glEnd();
}

void DrawQuad(float x1, float y1, float z1,  float x2, float y2, float z2) {
  glBegin(GL_QUADS);
  glEnd();
}
void DrawCube(float x1, float y1, float z1, float x2, float y2, float z2) {
  glBegin(GL_QUADS);
  glVertex3f(x1, y1, z1);
  glVertex3f(x1, y1, x2);
  glVertex3f(x1, y1, x2);
  glVertex3f(x1, y1, x2);
  glVertex3f(x1, y1, x2);
  glVertex3f(x1, y1, x2);
  glVertex3f(x1, y1, x2);
  glVertex3f(x1, y1, x2);
  glVertex3f(x1, y1, x2);
  glVertex3f(x1, y1, x2);
  glVertex3f(x1, y1, x2);
  glVertex3f(x1, y1, x2);
  glEnd();
}
void DrawTextGL(int x, int y, const char *text) {
  int width = glutGet(GLUT_WINDOW_WIDTH);
  int height = glutGet(GLUT_WINDOW_HEIGHT);
  glPushAttrib( GL_ALL_ATTRIB_BITS );
  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  gluOrtho2D(0, width, 0, height);
  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glLoadIdentity();

  glRasterPos2i(x, y);
  int len = strlen(text);
  for (int i = 0; i < len; ++i)
    glutBitmapCharacter(GLUT_BITMAP_9_BY_15, *(text +i));

  //glRasterPos2i(0, 0);
  glMatrixMode(GL_PROJECTION);
  glPopMatrix();
  glMatrixMode(GL_MODELVIEW);
  glPopMatrix();
  glPopAttrib();
  glFlush();
}

const std::string GetShaderPath(const char *filename) {
  std::string full_path(VenusOptions::project_path);
  full_path.append(filename);
  return full_path;
}
const std::string GetModelPath(const char *filename) {
  std::string full_path(VenusOptions::project_path);
 full_path.append(filename);
  return full_path;
}

GLuint MakeFullScreenQuad() {
  GLuint display_list = glGenLists(1);
  if (display_list) {
    glNewList(display_list, GL_COMPILE);
    
    // the vertex shader only need to pass the vertex through
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
    glRectf(-1, -1, 1, 1);
    glPopMatrix();
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
    glEndList();
  }
  return display_list;
}

void DisplayStastics(const char *name, float data, const char *unit, 
                     int x, int y) {
  char buff[256];
  sprintf(buff, "%s : %.3f %s", name, data, unit);
  glColor3f(0, 0, 0);
  DrawTextGL(x, y, buff);
  glColor3f(1, 1, 1);
  DrawTextGL(x+1, y+1, buff);
}

void VisualizeTexture(const ImageTextureGL &tex) {
  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glLoadIdentity();
  glMatrixMode(GL_TEXTURE);
  glPushMatrix();
  glLoadIdentity();
  tex.Enable();
  tex.Bind();
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE);
  // Show the shadowMap at its actual size relative to window
  glBegin(GL_QUADS);
  glTexCoord2f(0.0f, 0.0f);
  glVertex2f(-1.0f, -1.0f);
  glTexCoord2f(1.0f, 0.0f);
  glVertex2f(1, -1.0f);
  glTexCoord2f(1.0f, 1.0f);
  glVertex2f(1, 1);
  glTexCoord2f(0.0f, 1.0f);
  glVertex2f(-1.0f,  1);
  glEnd();
  tex.Unbind();
  tex.Disable();
  glPopMatrix();
  glMatrixMode(GL_PROJECTION);
  glPopMatrix();
  glMatrixMode(GL_MODELVIEW);
  glPopMatrix();
}

} // namespace venus
